/*
 * ConnectionPool class
 *
 * Authors: Mihael Bele, Interactive1
 *          Arsen Torbarina, Interactive1 - since v3.0
 *
 *   DD/MM/YY
 *   01/03/01       - mb - created
 *   26/10/01       - mb - appserver datasource support, default alias
 *   02/07/02       - at - pooling implemented
 *                       - Alias class changed to a nested Pool class
 *                       - synchronization added
 *                       - preferred cfg file is now ConnectionPool.cfg, instead of DBAliases.cfg
 *                       - closing open connections
 *                       - .....
 *   15/07/02 v3.11 - at - statistics, watchdog...
 *   24/07/02 v3.12 - at - logging
 *   05/09/02 v3.13 - at - defaultTimeout added
 *   14/09/02 v3.14 - at - library statistics added
 *                       - fixed bug when releasing the same connection more than once
 *
 * Copyright: (c) 2001-2002 Interactive1 Group
 */

package com.interactive1.dbutils;
import java.io.IOException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Map;
import java.util.Vector;

import com.interactive1.utils.Config;
import com.interactive1.utils.Log;
import com.interactive1.utils.MethodCallerExtractor;

/**
 * This class provides access to one or many connection pools defined
 * in a Property file. A client uses static methods to get or release
 * a connection.
 * 
 * <H2>Property file</H2>
 * The <B>Property file</B> contains configuration data for pools. The properties
 * are read using the <code>com.interactive1.utils.Config</code> class.
 * 
 * <UL>
 * <LI>The default property file is:<BR>
 * <CODE>com/interactive1/dbutils/ConnectionPool.cfg</CODE>
 * <LI>If the default property file was not found, the following one will be used instead:<BR>
 * <CODE>com/interactive1/dbutils/DBAliases.cfg</CODE>
 * </UL>
 * <P>
 * <TABLE WIDTH=100%>
 * <TR><TD><B>Parameter</B></TD><TD><B>Values</B></TD><TD><B>Description</B></TR>
 * <TR><TD>WATCHDOG</TD><TD><I>integer</I></TD><TD>Watchdog interval in seconds.</TD></TR>
 * <TR><TD>LIBRARIAN<TD><TD><I>string</I></TD><TD>Default Librarian classname.</TD></TR>
 * <TR><TD>SIMULATE<TD><TD><I>string</I></TD><TD>Librarian classname.</TD></TR>
 * <TR><TD><I>pool</I>.URL</TD><TD><I>url</I></TD><TD>Database URL.</TD></TR>
 * <TR><TD><I>pool</I>.DRIVER</TD><TD><I>full class name</I></TD><TD>Database driver.</TD></TR>
 * <TR><TD><I>pool</I>.USERNAME</TD><TD><I>string</I></TD><TD>Username used to log-it to the database.</TD></TR>
 * <TR><TD><I>pool</I>.PASSWORD</TD><TD><I>string</I></TD><TD>Password used to log-it to the database.</TD></TR>
 * <TR><TD><I>pool</I>.DATASOURCE</TD><TD><I>string</I></TD><TD>Datasource name (if using the application server's pool). If a datasource is used, all the other parameters will be ignored.</TD></TR>
 * <TR><TD><I>pool</I>.NOCONNECTIONS</TD><TD><I>integer</I></TD><TD>Maximum allowed number of connections in the pool. If 0 or none, there will be no size limit.</TD></TR>
 * <TR><TD><I>pool</I>.LIBRARIAN<TD><TD><I>string</I></TD><TD>Librarian classname.</TD></TR>
 * </TABLE>
 *
 * <H2>Requires</H2>
 * <UL>
 * <LI><code>com.interactive1.utils.<b>Log</b></code>
 * <LI><code>com.interactive1.utils.<b>Config</b></code>
 * </UL>
 *
 * @author Arsen Torbarina - Interactive1
 * @author Mihael Bele - Interactive1
 * @version v3.15 4.10.2002
 */
public class ConnectionPool
{
	/**
	* Class version info.
	* @since v3.10
	*/
	public static final String _VERSION = "v3.15";
	private static Hashtable pools = new Hashtable();
	private static String cf;
	private static boolean dismissed;	
	
	private static Watchdog watchdog;
	private static int watchdogInterval;
	private static boolean SIMULATE = false;
	
	private static Log log = Log.getInstance("ConnectionPool");
	
	private static long defaultTimeout = 0;
	private static String defaultLibName = null;
	
	private ConnectionPool() {
	}

	static {
		// First try opening the ConnectionPool.cfg file
		// If it fails, use the old one (DBAliases.cfg)

		log.write("ConnectionPool class loaded.");
		cf = "com/interactive1/dbutils/ConnectionPool.cfg";
		try {
			Config.mem.get(cf, "");
		} catch (IOException e)  {
			cf = "com/interactive1/dbutils/DBAliases.cfg";
		}
		//log.write(Log.DEBUG, "Using cfg file " + cf);
		try  {
			defaultTimeout = Config.mem.getInt(cf, "TIMEOUT") * 1000L;
		} catch (Exception ex1) {}
		try  {
			defaultLibName = Config.mem.get(cf, "LIBRARIAN").trim();
			if (defaultLibName.equals("")) defaultLibName = null;
		} catch (Exception ex1) {}
		try  {
			SIMULATE = Config.mem.get(cf, "SIMULATE").trim().substring(0,1).toUpperCase().equals("Y");
		} catch (Exception ex1) {}
		startWatchdog();
	}
	
	/**
	 * This method returns a connection from the named pool.
	 *
	 * The <code>name</code> is used to specify a predefined database connection
	 * parameters (e.g. for different databases or tablespaces).
	 *
	 * @param name Connection pool name
	 * @return A connection from the pool or null
	 * @throws java.lang.RuntimeException if the connection pool is dismised.
	 * @see #dismiss
	 */
	public static Connection getConnection(String name) {
		Pool pool = getPool(name);
		if (pool == null) {
		    return null;
		}
//		watchdog.resetTimer();
		Connection con = null;
		
		if (defaultTimeout == 0)  {
			con = pool.getConnection();
		} else  {
			con = pool.getConnection(defaultTimeout);
		}
		if (con == null) {
			log.write(1, Log.WARNING, "Could not get a connection from pool " + name);
		}
		return con;
	}
	
	/**
	 * Returns an open connection. If no one is available, and the max
	 * number of connections has not been reached, a new connection is
	 * created. If the max number has been reached, waits until one
	 * is available or the specified time has elapsed.
	 *
	 * @param name Connection pool name
	 * @param time The number of milliseconds to wait
	 * @return A connection from the pool or null
	 * @throws java.lang.RuntimeException if the connection pool is dismised.
	 * @see #dismiss
	 * @since v3.00
	 */
	public static Connection getConnection(String name, long time) {
		Pool pool = getPool(name);
	    if (pool == null) {
		    return null;
	    }
//		watchdog.resetTimer();
		Connection con = pool.getConnection(time);
		if (con == null) {
			log.write(1, Log.WARNING, "Could not get a connection from pool " + name);
		}
		return con;
	}
	
	private static Pool getPool(String name) {
		if (dismissed) {
			String em = "ConnectionPool is dismissed! To be used again, the class has to be re-published.";
			log.write(Log.ERROR, em);
			throw new RuntimeException(em);	
		}
		Pool pool = (Pool) pools.get(name);
		if (pool == null) {					// if not yet loaded
			pool = loadConfig(name);			// load it
		}
		if (pool == null) {
			log.write(Log.WARNING, "Could not find pool " + name);
		}
		return pool;		
	}
	
	/**
	* This method recreates a connection if for some reason the current one
	* should be replaced with a new one.
	*
	* @param name Connection pool name
	* @param con A connection to be recreated
	* @exception NullPointerException if a pool with the specified name was not found
	* @deprecated Should not be used anymore, since the the Connection Pool
	*             automatically replaces invalid connections.
	*/
	public static void recreate(String name, Connection con)
	{
		Pool pool = (Pool)pools.get(name);
		if (pool != null)  {
			pool.recreate(con);
		} else {
			failed(name, con);
		}
	}
	
	/**
	* Returns a connection to the named pool.
	*
	* @param name Connection pool name
	* @param con The Connection to be released
	*/
	public static void release(String name, Connection con)
	{
		Pool pool = (Pool)pools.get(name);
		if (pool != null)  {
			pool.release(con);
		} else {
			failed(name, con);
		}
	}

	private static void failed(String name, Connection con) {
		log.write(Log.WARNING, "Pool "+name+" not found!");
		try {
			//log.write(1, Log.DEBUG, "The connection will be closed.");
			con.close();
		} catch (Exception e) {
		}
	}

	/**
	* Returns a vector containing names of the active pools.
	*
	* @return A vector containing names of the active pools.
	* @since v3.10
	*/
	public static Vector getPoolNames() {
		Vector res = new Vector();
		synchronized (pools) {
			for (Enumeration e = pools.keys(); e.hasMoreElements();) {
				res.add((String)e.nextElement());
			}
		}
		return res;
	}

	/**
	* Returns a Map containing Librarian objects of the active pools.
	*
	* @return A Map of poolname-Librarian pairs containing a Librarian object for each pool.
	* @since v3.14
	*/
	public static Map getLibrarians() {
		Hashtable res = new Hashtable();
		synchronized (pools) {
			for (Iterator i = pools.values().iterator(); i.hasNext();) {
				Pool pool = (Pool)i.next();
				res.put(pool.getName(), pool.getLibrarian());
			}
		}
		return res;
	}

	/**
	* Returns a Librarian of the specified pool.
	*
	* @return A Librarian of the specified pool.
	* @since v3.14
	*/
	public static Librarian getLibrarian(String name) {
		Pool pool = (Pool) pools.get(name);
		if (pool == null)  {					// if not yet loaded
			pool = loadConfig(name);			// load it
		}
		if (pool == null) {
		    return null;
		}
		return pool.getLibrarian();		
	}

	/**
	* Sets a Librarian for the specified pool.
	*
	* @since v3.14
	*/
	public static void setLibrarian(String name, Librarian librarian) {
		Pool pool = (Pool) pools.get(name);
		if (pool == null)  {					// if not yet loaded
			pool = loadConfig(name);			// load it
		}
		if (pool == null) {
		    return;
		}
		pool.setLibrarian(librarian);		
	}


	/**
	* Gets a statistics for the named connection pool.
	* <H4>Supported parameters (keys):</H4>
	* <TABLE>
	* <TR><TD><B>Key</B></TD><TD><B>Data-Type</B></TD><TD><B>Description</B></TD></TR>
	* <TR><TD><B>NAME</B></TD><TD><code>String</code></TD><TD>connection pool name</TD></TR>
	* <TR><TD><B>USERNAME</B></TD><TD><code>String</code></TD><TD>database username</TD></TR>
	* <TR><TD><B>DRIVER</B></TD><TD><code>String</code></TD><TD>database driver</TD></TR>
	* <TR><TD><B>URL</B></TD><TD><code>String</code></TD><TD>database URL</TD></TR>
	* <TR><TD><B>DATASOURCE</B></TD><TD><code>String</code></TD><TD>database datasource</TD></TR>
	* <TR><TD><B>CAPACITY</B></TD><TD><code>Integer</code></TD><TD>pool capacity (maximum sum of free and occupied connections)</TD></TR>
	* <TR><TD><B>FREE</B></TD><TD><code>Integer</code></TD><TD>number of free connections in the pool</TR>
	* <TR><TD><B>OCCUPIED</B></TD><TD><code>Integer</code></TD><TD>number of occupied connections in the pool</TD></TR>
	* <TR><TD><B>LIBRARIAN</B></TD><TD><code>Librarian</code></TD><TD>the librarian, used for gathering statistics</TD></TR>
	* </TABLE>
	* <BR>
	*
	* @param name Connection pool name
	* @return A hashtable with the key-value pairs.
	* @since v3.10
	*/
	public static Hashtable getStat(String name) {
		Pool pool = (Pool) pools.get(name);
		if (pool == null)  {					// if not yet loaded
			pool = loadConfig(name);			// load it
		}
		if (pool == null) {
		    return null;
		}
		return pool.getStat();		
	}
	
	/**
	* Gets a general statistics for the connection pool manager.
	* <H4>Supported parameters (keys):</H4>
	* <TABLE>
	* <TR><TD><B>Key</B></TD><TD><B>Data-Type</B></TD><TD><B>Description</B></TD></TR>
	* <TR><TD><B>VERSION</B></TD><TD><code>String</code></TD><TD>ConnectionPool class version number</TD></TR>
	* <TR><TD><B>POOLS</B></TD><TD><code>Integer</code></TD><TD>number of active pools</TD></TR>
	* <TR><TD><B>POOL_NAMES</B></TD><TD><code>Vector</code></TD><TD>vector containing pool names</TD></TR>
	* <TR><TD><B>DISMISSED</B></TD><TD><code>Boolean</code></TD><TD>is the ConnectionPool class dismissed</TD></TR>
	* <TR><TD><B>WATCHDOG</B></TD><TD><code>Boolean</code></TD><TD>is the watchdog active</TD></TR>
	* <TR><TD><B>WD_INTERVAL</B></TD><TD><code>Integer</code></TD><TD>watchdog interval in seconds</TD></TR>
	* <TR><TD><B>TIMEOUT</B></TD><TD><code>Integer</code></TD><TD>default getConnection timeout in seconds</TD></TR>
	* </TABLE>
	* <BR>
	*
	* @return A hashtable with the key-value pairs.
	* @since v3.10
	*/
	public static Hashtable getStat() {
		Hashtable res = new Hashtable();
		Vector pn = getPoolNames();
		res.put("VERSION", new String(_VERSION));
		res.put("POOLS", new Integer(pn.size()));
		res.put("POOL_NAMES", pn);
		res.put("DISMISSED", new Boolean(dismissed));
		res.put("WATCHDOG", new Boolean(watchdog!=null && !watchdog.isTerminated()));
		res.put("WD_INTERVAL", new Integer(watchdogInterval));
		res.put("TIMEOUT", new Integer((int)(defaultTimeout/1000)));
		return res;
	}

	/**
	* Closes all the connections from the named pool.
	*
	* @param name Connection pool name
	* @since v3.10
	*/
	public static void closeAll(String name) {
		Pool pool = (Pool) pools.get(name);
		if (pool == null) {
			return;
		}
		pool.closeAll();
	}

	/**
	* Closes all the free connections from the named pool.
	*
	* @param name Connection pool name
	* @since v3.10
	*/
	public static void closeFree(String name) {
		Pool pool = (Pool) pools.get(name);
		if (pool == null) {
			return;
		}
		pool.closeFree();
	}

	/**
	* Closes all the free connections from all the pools.
	*
	* @since v3.10
	*/
	public static void closeFree() {
		//log.write(1, Log.DEBUG, "Closing all the free connections from all the pools...");
		synchronized (pools) {
			for (Enumeration e = pools.elements(); e.hasMoreElements();) {
				Pool pool = (Pool) e.nextElement();
				pool.closeFree();
			}
		}
	}

	/**
	* Closes all the open connections from the named pool and checks the open ones for
	* validity. The invalid or broken connections get closed.
	*
	* @param name Connection pool name
	* @since v3.10
	*/
	public static void tidy(String name) {
		//log.write(1, Log.DEBUG, "Tidying up pool " + name + "...");
		Pool pool = (Pool) pools.get(name);
		if (pool == null) {
			return;
		}
		pool.closeFree();
		pool.checkOccupied();
	}
	
	/**
	* Closes all the open connections from all the pools and checks the open ones for
	* validity. The invalid or broken connections get closed.
	*
	* @since v3.10
	*/
	public static void tidy() {
		//log.write(1, Log.DEBUG, "Tidying up all pools...");
		tidy(true);
	}

	private static void tidy(boolean nolog) {
		if (nolog) {}
		synchronized (pools) {
			for (Enumeration e = pools.elements(); e.hasMoreElements();) {
				Pool pool = (Pool) e.nextElement();
				pool.closeFree();
				pool.checkOccupied();
			}
		}
	}
	
	/**
	* Closes all the connections from the named pool and removes it from the cache.
	*
	* @param name Connection pool name
	* @since v3.10
	*/
	public static void remove(String name) {
		Pool pool = (Pool) pools.get(name);
		if (pool == null) {
			return;
		}
		synchronized (pool) {
			pool.closeAll();
			pools.remove(name);
		}
	}

	private static synchronized Pool loadConfig(String name)
	{
		//log.write(Log.DEBUG, "Refreshing Config & reloading log properties...");
		Config.mem.refresh();
		log.readProperties();
		
		//log.write(Log.DEBUG, "Loading configuration for pool " + name + "...");
		try {
			String username   = Config.mem.get(cf, name+".USERNAME");
			String password   = Config.mem.get(cf, name+".PASSWORD");
			String driver     = Config.mem.get(cf, name+".DRIVER");
			String url        = Config.mem.get(cf, name+".URL");
			String datasource = Config.mem.get(cf, name+".DATASOURCE");
			String nc         = Config.mem.get(cf, name+".NOCONNECTIONS");
			String libName    = defaultLibName;
			try  {
				libName = Config.mem.get(cf, name+".LIBRARIAN").trim();
				if (libName.equals("")) libName = null;
			} catch (Exception e) {
			}

			if (driver==null && datasource==null)  {
				throw new IllegalArgumentException("bad configuration for pool: "+name);
			}

			int nocon = 0;
			try {
				nocon = Integer.parseInt(nc);
			} catch (Exception e) {
			}

			Pool pool = new Pool(name, username, password, driver, url, nocon, datasource, libName);
			pools.put(name, pool);
			return pool;
		} catch (Exception e) {
			log.write(Log.ERROR, "Error retreiving database pool parameters: " + e);
			e.printStackTrace();
		}
		return null;
	}
	
	/**
	* This method closes all open connections and disables the ConnectionManager to open
	* new connections. It has to be called before re-publishing the class, to ensure
	* that all the opened connections close.
	*
	* <P>Once this method was called, the class has to be re-published, to be used again.
	* This is a security-mechanism that blocks creation of new connections, once when the
	* ConnectionPool has closed all the open connections.
	*
	* @since v3.00
	*/
	public static void dismiss() {
		//log.write(Log.DEBUG, "Dismissing the ConnectionPool...");
		dismissed = true;
		stopWatchdog();
		closeAll();
		log.write("ConnectionPool dismissed. The class has to be re-published to be used again!");
	}

	/**
	* Closes all the connections from all the pools. The pools remain unchanged.
	*
	* @since v3.00
	*/
	public static void closeAll() {
		//log.write(Log.DEBUG, "Closing all pools...");
		synchronized (pools) {
			for (Enumeration e=pools.elements(); e.hasMoreElements();) {
				Pool pool = (Pool) e.nextElement();
				pool.closeAll();
			}
		}
	}

	/**
	* Removes all the pools and closes all their connections. All the pools are removed.
	*
	* @since v3.00
	*/
	public static void removeAll() {
		synchronized (pools) {
			closeAll();
			pools.clear();
		}
	}
	
	/**
	* Starts the watchdog. The watchdog is a thread that calls the <code>ConnnectionPool.tidy()</code> method
	* in given intervals.
	*
	* <P>There can be only one watchdog running, so before running a new watchdog, it sends
	* a terminate signal to the old one (if any) and waits until it is terminated.
	*
	* <P>The watchdog interval (in seconds) is taken from the WATCHDOG
	* parameter from the config file, or the last used interval if not found in the config file.
	*
	* @see #tidy()
	* @see #startWatchdog(int)
	* @since v3.10
	*/
	public static synchronized void startWatchdog() {
		try {
			String wd = Config.mem.get(cf, "WATCHDOG");
			watchdogInterval = Integer.parseInt(wd);
		} catch (Exception e) {
		}
		if (watchdogInterval>0) {
			startWatchdog(watchdogInterval);
		}
	}

	/**
	* Stops the watchdog.
	*
	* @see #startWatchdog()
	* @since v3.10
	*/
	public static void stopWatchdog() {
		if (watchdog != null) watchdog.terminate();
	}

	/**
	* Starts the watchdog.
	*
	* @param interval the watchdog interval in seconds
	* @see #startWatchdog()
	* @since v3.10
	*/
	public static synchronized void startWatchdog(int interval) {
		if (watchdog != null) {
			watchdog.terminate();
			while (!watchdog.isTerminated()) {
				Thread.yield();
			}	
		}
		watchdogInterval = interval;
		watchdog = new Watchdog(watchdogInterval);
		watchdog.start();
	}

	/**
	* This inner class represents a single connection pool. It creates new
	* connections on demand, up to a max number if specified.
	* It also makes sure a connection is still open before it is
	* returned to a client.
	*/
	static class Pool {
		private String name;
		private String username;
		private String password;
		private String driver;
		private String url;
		private String datasource;
		private int maxConn;

		private Vector freeConnections = new Vector();
		private Vector occupiedConnections = new Vector();
		private Librarian librarian;

		/**
		* Creates new connection pool.
		*
		* @param URL The JDBC URL for the database
		* @param user The database user, or null
		* @param password The database user password, or null
		* @param maxConn The maximal number of connections, or 0
		*   for no limit
		*/
		protected Pool(String name, String username, String password, String driver, String url, int maxConn, String datasource, String libName) throws Exception {
			this.name = name;
			this.username = username;
			this.password = password;
			this.driver = driver;
			this.url = url;
			this.maxConn = maxConn;
			this.datasource = datasource;
			if ("".equals(datasource)) datasource = null;
			if (datasource == null) {
				//log.write(1, Log.DEBUG, "["+name+"] Created pool (" + username + " @ " + url + ") with " + (maxConn == 0 ? "unlimited number of" : "up to "+maxConn) + " connections.");
			} else {
				//log.write(1, Log.DEBUG, "["+name+"] Created pool using datasource " + datasource);
			}
			
			if (libName != null) {
				try {
					this.setLibrarian((Librarian)Class.forName(libName).newInstance());
				} catch (Exception e1) {
					log.write(Log.ERROR, "["+name+"] Failed loading Librarian "+libName+": "+e1);	
					e1.printStackTrace();					

					if (defaultLibName != null && libName!=defaultLibName) {
						libName = defaultLibName;
						try {
							this.setLibrarian((Librarian)Class.forName(libName).newInstance());
						} catch (Exception e2) {
							log.write(Log.ERROR, "["+name+"] Failed loading default Librarian "+libName+": "+e2);
							e2.printStackTrace();
						}
					}
				}
			}
			if (librarian != null) {
				//log.write(1, Log.DEBUG, "["+name+"] Using Librarian "+librarian.getClass().getName());	
			}
		}
		
		/**
		* Returns a pool name.
		* @return pool name
		* @since v3.14
		*/
		protected String getName() {
			return name;
		}

		/**
		* Fetches a connection from the pool. If no free connection
		* is available, a new connection is created unless the max
		* number of connections has been reached. If a free connection
		* has been closed by the database, it's removed from the pool
		* and this method is called again recursively.
		*/
		protected synchronized Connection getConnection() {
			//log.write(2, Log.DEBUG, "["+name+"] getConnection request");
			Connection con = getConnectionPrivate();
			if (librarian != null) librarian.checkOut(MethodCallerExtractor.getCaller(3), con);
			return con;
		}

		private Connection getConnectionPrivate() {
			Connection con = null;
			if (datasource != null) {
				//log.write(2, Log.DEBUG, "["+name+"] getConnectionPrivate from datasource " + datasource);
				return newConnection();
			}
			//log.write(2, Log.DEBUG, "["+name+"] getConnectionPrivate request ("+freeConnections.size()+" free, "+occupiedConnections.size()+" occupied, maximum "+maxConn+").");
			if (freeConnections.size() > 0) {
				//log.write(3, Log.DEBUG, "["+name+"] Trying to get a free connection...");
				// Pick the first Connection in the Vector
				// to get round-robin usage
				con = (Connection) freeConnections.firstElement();
				freeConnections.removeElementAt(0);
				try {
				    if (con.isClosed()) {
				        //log.write(3, Log.DEBUG, "["+name+"] This one is closed! Trying again...");
						close(con);
				        // Try again recursively
				        con = getConnectionPrivate();
				    }
				}
			    catch (SQLException e) {
					//log.write(3, Log.DEBUG, "["+name+"] This one is bad! Closing it and trying again...");
					close(con);
					// Try again recursively
					con = getConnectionPrivate();
			    }
			} else if (maxConn == 0
				 || freeConnections.size() + occupiedConnections.size() < maxConn) {
			    con = newConnection();
			}
			if (con != null) {
				//log.write(2, Log.DEBUG, "["+name+"] Got a connection!");
				if (!occupiedConnections.contains(con)) occupiedConnections.addElement(con);
			} else {
				//log.write(2, Log.DEBUG, "["+name+"] No available connections.");
			}
			return con;
		}
		
		/**
		 * Fetches a connection from the pool. If no free connection
		 * is available, a new connection is created unless the max
		 * number of connections has been reached. If a free connection
		 * has been closed by the database, it's removed from the pool
		 * and this method is called again recursively.
		 * <P>
		 * If no connection is available and the max number has been 
		 * reached, this method waits the specified time for one to be
		 * checked in.
		 *
		 * @param timeout The timeout value in milliseconds
		 * @since v3.00
		 */
		protected synchronized Connection getConnection(long timeout) {
		    long startTime = System.currentTimeMillis();
		    //log.write(2, Log.DEBUG, "["+name+"] getConnection("+timeout+") request.");
		    Connection con;
		    while ((con = getConnectionPrivate()) == null) {
		        //log.write(2, Log.DEBUG, "["+name+"] Waiting up to "+timeout+" ms for an available connection...");
		        try {
		            wait(timeout);
		        }
		        catch (InterruptedException e) {}
		        if ((System.currentTimeMillis() - startTime) >= timeout) {
		            // Timeout has expired
		            //log.write(2, Log.DEBUG, "["+name+"] Time expired -- no available connections in given time.");
		            return null;
		        }
		    }
		    if (librarian != null) librarian.checkOut(MethodCallerExtractor.getCaller(3), con);
		    return con;
		}

		/**
		* Returns a connection to the pool. Notifies other Threads that
		* may be waiting for a connection.
		*
		* @param con The connection to be released
		*/
		protected synchronized void release(Connection con)	{
			if (con==null) return;
			if (librarian != null) librarian.checkIn(MethodCallerExtractor.getCaller(3), con);
			if (datasource == null) {
				occupiedConnections.removeElement(con);
				if (maxConn==0 || occupiedConnections.size()+freeConnections.size()<maxConn) {
					if (!freeConnections.contains(con)) freeConnections.addElement(con);
				} else {
					close(con);					
				}
				//log.write(2, Log.DEBUG, "["+name+"] Released a connection ("+freeConnections.size()+" free, "+occupiedConnections.size()+" occupied, maximum "+maxConn+").");
			} else {
				//log.write(2, Log.DEBUG, "["+name+"] Closing connection to datasource " + datasource + "...");
				close(con);
			}
			notifyAll();
		}

		private void close(Connection con) {
			if (librarian != null) librarian.forget(con);
			
			try {
				con.close();
				//log.write(2, Log.DEBUG, "["+name+"] Connection closed.");
			} catch (Exception e) {
				log.write(1, Log.ERROR, "["+name+"] Unable to close connection.");
			} finally {
				con = null;
			}
		}

		/**
		*	this method will be changed when connection pool is implemented
		*	this method recreates connection if for some reason current one should be replaced with new one
		*/
		protected synchronized void recreate(Connection con)
		{
			log.write(2, Log.WARNING, "["+name+"] Called recreate() -- OBSOLETE!");

			if (con==null) return;
			if (datasource == null) {
				occupiedConnections.removeElement(con);
			}	
			close(con);
			notifyAll();

		/*		int i = connections.indexOf(con);
				connections.removeElementAt(i);
				close(con);
				con = newConnection();
				connections.insertElementAt(con,i);
		*/
		}

		private Connection newConnection() {
			//log.write(3, Log.DEBUG, "["+name+"] Creating a new connection...");
			Connection con = null;

			if (SIMULATE) return new DummyConnection();

			try {
				if (datasource == null) {
					Class.forName(driver);
					con = DriverManager.getConnection(url, username, password);
				} else {
					javax.naming.InitialContext context = null;
					context = new javax.naming.InitialContext();
					javax.sql.DataSource ds = (javax.sql.DataSource) context.lookup(datasource);
					if (username != null && password != null)  {
						con = ds.getConnection(username, password);
					} else  {
						con = ds.getConnection();
					}
				}
			} catch (Exception e) {
				log.write(1, Log.ERROR, "["+name+"] Cannot open a connection: " + e);
				e.printStackTrace();
			}
			//if (con!=null) log.write(3, Log.DEBUG, "["+name+"] New connection created!");
			return con;
		}

		/**
		* Closes and removes all opened connections from the pool.
		*/
		protected synchronized void closeAll() {
			closeOccupied();
			closeFree();
		}
		
		/**
		* Closes and removes all free connections from the pool.
		*/
		protected synchronized void closeFree() {
			//log.write(3, Log.DEBUG, "["+name+"] Closing " + freeConnections.size() + " free connections...");
			for (Enumeration en=freeConnections.elements(); en.hasMoreElements();)  {
				close((Connection)en.nextElement());
			}
			freeConnections.clear();
			notifyAll();
		}

		/**
		* Closes and removes all occupied connections from the pool.
		*/
		protected synchronized void closeOccupied() {
			if (librarian!=null) librarian.reset();
			//log.write(3, Log.DEBUG, "["+name+"] Closing " + occupiedConnections.size() + " occupied connections...");
			for (Enumeration en=occupiedConnections.elements(); en.hasMoreElements();)  {
				close((Connection)en.nextElement());
			}
			occupiedConnections.clear();
			notifyAll();
		}

		protected void finalize() throws Throwable {
			//log.write(2, Log.DEBUG, "["+name+"] Finalizing...");
			this.closeAll();
			freeConnections = null;
			occupiedConnections = null;
			super.finalize();
			System.gc();
			//log.write(1, Log.DEBUG, "["+name+"] Finalized!");
		}
		
		protected Hashtable getStat() {
			Hashtable res = new Hashtable();
			res.put("NAME", ""+name);
			res.put("USERNAME", ""+username);
			res.put("DRIVER", ""+driver);
			res.put("URL", ""+url);
			if (datasource!=null) {
				res.put("DATASOURCE", ""+datasource);
			} else {
				res.put("CAPACITY", new Integer(maxConn));
				res.put("FREE", new Integer(freeConnections.size()));
				res.put("OCCUPIED", new Integer(occupiedConnections.size()));
			}
			if (librarian!=null) res.put("LIBRARIAN", librarian);
			return res;
		}
		
		protected Librarian getLibrarian() {
			return librarian;
		}		

		protected void setLibrarian(Librarian librarian) {
			if (librarian==null) {
				log.write("["+name+"] Using no librarian");
			} else {
				log.write("["+name+"] Using librarian: "+librarian.getClass().getName());
			}
			this.librarian = librarian;
		}

		protected synchronized void checkOccupied() {
			occupiedConnections = checkForValidity(occupiedConnections);
			notifyAll();
		}
		
		protected synchronized void checkFree() {
			freeConnections = checkForValidity(freeConnections);
			notifyAll();
		}
		
		private Vector checkForValidity(Vector conns) {
			Vector res = new Vector();
			//log.write(2, Log.DEBUG, "["+name+"] Tyding up " +
			//	 (conns == occupiedConnections ? "occupied" : "free") + " connections...");
			for (Enumeration en = conns.elements(); en.hasMoreElements();) {
				Connection con = (Connection)en.nextElement();				
				try {
				    if (con.isClosed()) {
				        //log.write(3, Log.DEBUG, "["+name+"] Found a closed connection!");
						close(con);
				    } else {
				    	res.add(con);
				    }
				}
				catch (SQLException e) {
					//log.write(3, Log.DEBUG, "["+name+"] Found a bad connection!");
					close(con);
				}
			}
			//log.write(2, Log.DEBUG, "["+name+"] Tyding up is done!");
			return res;		
		}
	}

	static class Watchdog extends Thread {
		private long interval;
		private boolean terminated = false;
		private boolean terminateSignal = false;
		
		
		protected Watchdog(int interval) {
			this.interval = interval*1000;
		}
		
		public void run() {
			log.write("Watchdog started.");
			while (!terminateSignal) {
				try {
					Thread.sleep(interval);			
					//log.write(2, Log.DEBUG, "Watchdog tidying-up...");
					ConnectionPool.tidy(true);
				} catch (InterruptedException e) {
				}
			}
			terminated = true;
			log.write("Watchdog terminated.");
		}

		public void resetTimer() {
			this.interrupt();
		}
		
		public void terminate() {
			log.write(Log.DEBUG, "Watchdog sending terminate signal...");
			terminateSignal = true;
			this.interrupt();
		}
		
		public boolean isTerminated() {
			return terminated;
		}
	}
	
}